Skip to content

fix(serialization): fix ToolInfo.ParamsOneOf loss during checkpoint deep-copy#1007

Merged
shentongmartin merged 1 commit intoalpha/09from
fix/checkpoint-serialization
May 6, 2026
Merged

fix(serialization): fix ToolInfo.ParamsOneOf loss during checkpoint deep-copy#1007
shentongmartin merged 1 commit intoalpha/09from
fix/checkpoint-serialization

Conversation

@shentongmartin
Copy link
Copy Markdown
Contributor

@shentongmartin shentongmartin commented Apr 30, 2026

Summary

Problem Solution
ToolInfo.ParamsOneOf lost after checkpoint deep-copy during interrupt/resume Fix InternalSerializer to pass pointer to json.Marshal, enabling pointer-receiver MarshalJSON

Key Insight

InternalSerializer.internalMarshal uses reflection to detect types implementing json.Marshaler and delegates to json.Marshal. However, it passes the result of rv.Interface() — a non-addressable struct value. Go's method dispatch requires an addressable value to call pointer-receiver methods. Since ToolInfo.MarshalJSON is defined on *ToolInfo, the non-addressable ToolInfo value does NOT satisfy json.Marshaler. json.Marshal falls back to default struct encoding, which cannot access the unexported ParamsOneOf fields — causing silent data loss.

The fix ensures we always pass a pointer to json.Marshal:

  • If rv.CanAddr(): use rv.Addr().Interface() directly
  • Otherwise: allocate a temporary reflect.New(rt), copy the value, and pass the pointer

Changes

internal/serialization/serialization.go — In the checkMarshaler branch of internalMarshal, ensure json.Marshal always receives a pointer so that pointer-receiver MarshalJSON methods are invoked.

Testing

  • Existing adk interrupt/cancel tests pass with -race (200+ iterations)
  • Existing compose tests pass with -race
  • The fix is exercised whenever any type with pointer-receiver MarshalJSON is stored in graph state and checkpointed

摘要

问题 方案
ToolInfo.ParamsOneOf 在 interrupt/resume checkpoint 深拷贝后丢失 修复 InternalSerializer,确保传递指针给 json.Marshal,使指针接收者 MarshalJSON 能被调用

核心洞察

InternalSerializer.internalMarshal 通过反射检测实现了 json.Marshaler 的类型,然后委托给 json.Marshal。但它传递的是 rv.Interface() 的结果——一个不可寻址的结构体值。Go 的方法分派要求可寻址的值才能调用指针接收者方法。由于 ToolInfo.MarshalJSON 定义在 *ToolInfo 上,不可寻址的 ToolInfo 值不满足 json.Marshaler 接口。json.Marshal 回退到默认结构体编码,无法访问未导出的 ParamsOneOf 字段——导致静默数据丢失。

修复确保始终传递指针给 json.Marshal

  • rv.CanAddr():直接使用 rv.Addr().Interface()
  • 否则:分配临时 reflect.New(rt),复制值,传递指针

变更

internal/serialization/serialization.go — 在 internalMarshalcheckMarshaler 分支中,确保 json.Marshal 始终接收指针,以调用指针接收者 MarshalJSON 方法。

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (alpha/09@1df3092). Learn more about missing BASE report.

Additional details and impacted files
@@             Coverage Diff             @@
##             alpha/09    #1007   +/-   ##
===========================================
  Coverage            ?   82.95%           
===========================================
  Files               ?      162           
  Lines               ?    22098           
  Branches            ?        0           
===========================================
  Hits                ?    18332           
  Misses              ?     2532           
  Partials            ?     1234           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shentongmartin shentongmartin force-pushed the fix/checkpoint-serialization branch 2 times, most recently from 6c1d86a to 58b8751 Compare April 30, 2026 04:28
… InternalSerializer

When InternalSerializer marshals a struct value that implements
json.Marshaler via pointer receiver (e.g. *ToolInfo), rv.Interface()
produces a non-addressable copy. json.Marshal then cannot call the
pointer method and falls back to default struct encoding, which skips
unexported fields — causing ParamsOneOf data loss after deepCopyState
during interrupt/resume.

Fix: pass a pointer to json.Marshal by using rv.Addr() when addressable,
or copying into reflect.New() otherwise.

Change-Id: Ib325f5cbb1f97271f1609d8f29b9669e9ec01d60
@shentongmartin shentongmartin force-pushed the fix/checkpoint-serialization branch from 58b8751 to c872aa4 Compare May 6, 2026 01:55
@shentongmartin shentongmartin changed the title fix(compose): fix ToolInfo.ParamsOneOf loss and eliminate deep-copy in checkpoint serialization fix(serialization): fix ToolInfo.ParamsOneOf loss during checkpoint deep-copy May 6, 2026
@shentongmartin shentongmartin merged commit 20891cd into alpha/09 May 6, 2026
16 checks passed
@shentongmartin shentongmartin deleted the fix/checkpoint-serialization branch May 6, 2026 03:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants